Categories
JavaScript Best Practices

Better JavaScript — Methods and this

Spread the love

Like any kind of apps, JavaScript apps also have to be written well.

Otherwise, we run into all kinds of issues later on.

In this article, we’ll look at ways to improve our JavaScript code.

Store Methods on Prototypes

We should store methods in the prototype of a constructor.

This way, we only store one copy of a method and each instance inherit from the prototype.

For instance, instead of writing:

function Person(name) {
  this.name = name;

  this.toString = function() {
    return `Person: ${this.name}`;
  };
}

We write:

function Person(name) {
  this.name = name;
}

Person.prototype.toString = function() {
  return `Person: ${this.name}`;
};

On the surface, they’re the same.

We get the toString method either way if we create a new Person instance with the new operator.

But under the surface, the second way is more efficient.

Use Closures to Store Private Data

JavaScript can have functions inside functions.

And data inside functions aren’t accessible to the outside.

So we can put private data inside functions.

For instance, if we have:

(() => {
  let x = 1;

  function add(y) {
    return x + y;
  }
})();

We have the add function inside the arrow function.

And we get the x value inside the add function.

This way, x won’t be available to the outside.

Store Instance State on Instance Objects

We store instance state on instance objects only.

For instance, we shouldn’t have code like:

function Tree() {}

Tree.prototype = {
  children: [],
  addChild(x) {
    this.children.push(x);
  }
};

Instead, we should write:

function Tree() {
  this.children = [];
}

Tree.prototype = {
  addChild(x) {
    this.children.push(x);
  }
};

The prototype is what Tree inherits from, so we shouldn’t put our state there since it’s shared by all instances of Tree .

Instead, we should add states to the constructor so that they won’t be shared in the instance.

Mutable data are always problematic when they’re shared.

This mistake can be avoided with the class syntax.

So we can write:

class Tree {
  constructor() {
    this.children = [];
  }

  addChild(x) {
    this.children.push(x);
  }
}

This way, there’s no way to create states that are shared by multiple instances.

Implicit Binding of this

this is something that can have different values at different locations.

If it’s at the top level, then this is the global object if strict mode is off and undefined if it’s on.

this is the constructor if it’s in a prototype’s method.

And in a class, then this is the class itself.

So if we have:

function Tree() {
  this.children = [];
}

Tree.prototype = {
  addChild(x) {
    this.children.push(x);
  }
};

then this is the Tree constructor.

If we have callbacks that are traditional functions, then this would be the callback itself.

Therefore, we should be aware of their values so that we won’t refer to the wrong value of this .

Arrow functions don’t bind to their own value of this so they’re great for callbacks.

Conclusion

We should be careful of where to place our constructor states.

Also, the value of this may change depending on context.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *